use crate::fio::IoManager;
use log::error;
use parking_lot::RwLock;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::os::unix::fs::FileExt;
use std::path::PathBuf;
use std::sync::Arc;

pub struct FileIO {
    fd: Arc<RwLock<File>>,
}

impl FileIO {
    pub fn new(file_name: PathBuf) -> crate::errors::Result<Self> {
        match OpenOptions::new()
            .create(true)
            .read(true)
            .write(true)
            .append(true)
            .open(file_name)
        {
            Ok(fd) => {
                return Ok(FileIO {
                    fd: Arc::new(RwLock::new(fd)),
                });
            }
            Err(e) => {
                error!("failed open file error: {:?}", e);
                return Err(crate::errors::Errors::FailedToOpenDataFileError);
            }
        }
    }
}

impl IoManager for FileIO {
    fn read(&self, buf: &mut [u8], offset: u64) -> crate::errors::Result<usize> {
        let read_guard = self.fd.read();
        match read_guard.read_at(buf, offset) {
            Ok(n) => return Ok(n),
            Err(e) => {
                error!("read data file error: {:?}", e);
                return Err(crate::errors::Errors::FailedToReadDataFileError);
            }
        }
    }

    fn write(&self, buf: &[u8]) -> crate::errors::Result<usize> {
        let mut write_guard = self.fd.write();
        match write_guard.write(buf) {
            Ok(n) => return Ok(n),
            Err(e) => {
                error!("write data file error: {:?}", e);
                return Err(crate::errors::Errors::FailedToWriteDataFileError);
            }
        }
    }

    fn sync(&self) -> crate::errors::Result<()> {
        let read_guard = self.fd.read();
        if let Err(e) = read_guard.sync_all() {
            error!("sync data file error: {:?}", e);
            return Err(crate::errors::Errors::FailedToSyncDataFileError);
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use crate::fio::file_io::FileIO;
    use crate::fio::IoManager;
    use std::path::PathBuf;

    #[test]
    fn test_file_write() {
        let path = PathBuf::from("a.data");
        let mut fileio = FileIO::new(path);
        assert!(fileio.is_ok());
        let fio = fileio.ok().unwrap();
        let res1 = fio.write("key-a".as_bytes());
        assert!(res1.is_ok());
    }
}
